package com.txtsqlclient.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.txtsqlclient.dataaccess.file.DataSourceTypeDAO;

/**
 * @author spadda
 */
public class DataSourceType {

	/** Identifier. */
	private final String identifier;

	/** Name. */
	private final String name;

	/** Class name of the Driver. */
	private final String driverClassName;

	/** URL format for the connection. */
	private final String connectionURL;

	/** Boolean to specify if the type is database of file. */
	private final boolean isDatabase;

	protected static Map<String, DataSourceType> types;

	protected static List<DataSourceType> typesList;

	private final static Log LOGGER = LogFactory.getLog(DataSourceType.class);

	public DataSourceType(final String identifier, final String name,
			final String driverClassName, final String connectionURL,
			final boolean isDatabase) throws ConfigurationException {
		try {
			Class.forName(driverClassName);
		} catch (ClassNotFoundException e) {
			throw new ConfigurationException("The JDBC driver for class: "
					+ driverClassName + " is not found in the classpath.", e);
		}
		this.identifier = identifier;
		this.name = name;
		this.driverClassName = driverClassName;
		this.connectionURL = connectionURL;
		this.isDatabase = isDatabase;
	}

	/**
	 * This method is required to resolve a Data Source Type based on the
	 * identifier.
	 * 
	 * @param dsTypeIdentifier
	 * @return The Data Souce Type with the given identifier.
	 */
	public static DataSourceType resolveDataSourceType(
			final String dsTypeIdentifier) {
		getAllTypes();
		DataSourceType matchedDsType = types.get(dsTypeIdentifier);
		LOGGER.debug("Resolved Data Source type: " + matchedDsType
				+ " from identifier: " + dsTypeIdentifier);
		return matchedDsType;
	}

	/**
	 * This method is required to get all configured Data Source Types. In
	 * addition, this is the method which will check if the types have been
	 * initialized, and would initialize them if required.
	 * 
	 * @return The complete list of Data Source Types configured.
	 */
	public static List<DataSourceType> getAllTypes() {
		if (types == null) {
			initializeTypes();
		} else {
			LOGGER.debug("Data Source Types have already been read from file.");
		}
		if (typesList == null) {
			typesList = new ArrayList<DataSourceType>(types.values());
		}
		return typesList;
	}

	/**
	 * This method is required to initialize the Data Source Types.
	 */
	protected static void initializeTypes() {
		DataSourceTypeDAO dao = DataSourceTypeDAO.getInstance();
		Map<String, DataSourceType> typesMap = new LinkedHashMap<String, DataSourceType>();
		for (DataSourceType type : dao.getDataSourceTypes()) {
			if (typesMap.containsKey(type.getIdentifier())) {
				LOGGER.info("Data Source Type with identifier "
						+ type.getIdentifier()
						+ " is already present. The new type is not loaded");
			} else {
				typesMap.put(type.getIdentifier(), type);
			}
		}
		types = Collections.unmodifiableMap(typesMap);
	}

	public String getConnectionURL(final DataSource ds) {
		return ds.formatURL(this.connectionURL);
	}

	public String toString() {
		final StringBuffer buf = new StringBuffer();
		buf.append("[Identifier:");
		buf.append(this.identifier);
		buf.append(", Name:");
		buf.append(this.name);
		buf.append(", Driver:");
		buf.append(this.driverClassName);
		buf.append(", connectionURL:");
		buf.append(this.connectionURL);
		buf.append("]");
		return buf.toString();
	}

	/**
	 * @return the identifier
	 */
	public String getIdentifier() {
		return identifier;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @return the isDatabase
	 */
	public boolean isDatabase() {
		return isDatabase;
	}

	public int hashCode() {
		final int prime = 31;
		int hashCode = 7;
		hashCode = prime * hashCode + this.identifier.hashCode();
		hashCode = prime * hashCode + this.name.hashCode();
		hashCode = prime * hashCode + this.driverClassName.hashCode();
		hashCode = prime * hashCode + this.connectionURL.hashCode();
		hashCode = prime * hashCode + (this.isDatabase ? 1 : 0);
		return hashCode;
	}

	public boolean equals(final Object obj) {
		if (obj instanceof DataSourceType) {
			DataSourceType dst = (DataSourceType) obj;
			if (this.identifier.equals(dst.identifier)
					&& this.name.equals(dst.name)
					&& this.driverClassName.equals(dst.driverClassName)
					&& this.connectionURL.equals(dst.connectionURL)
					&& this.isDatabase == dst.isDatabase) {
				return true;
			}
		}
		return false;
	}
}
